{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 小美 - 企业微信预约机器人\n",
    "\n",
    "使用PaddleNLP，Wechaty和Rasa制作基于企业微信的智能预约机器人\n",
    "\n",
    "[项目GitHub地址](https://github.com/lhr0909/appointment-bot)\n",
    "\n",
    "## 设想初衷\n",
    "\n",
    "我经常去一家日式预约制理发店，和老板交谈后发现，他们不希望能够让散客立刻能够预约店长，并且需要能够快速管理所有的会员，并且能够分开不同的会员梯度。我们认为，通过有赞和大众点评并不能控制准入的客人，如果使用企业微信，并对联系人进行标签管理，既可以达到管理会员的需要，也不丢失预约制理发店的特质。另外我发现，理发店的日程管理非常原始，采用纸质管理，并且因为理发店工作人员不多，不一定能够及时提醒顾客到店，会产生丢单的情况。所以我们和理发店老板商量希望帮他们在企业微信上线一个预约机器人，把预约日程记录到企业微信的日历里，并且能够利用微信功能自动提醒。\n",
    "\n",
    "## 对话流程设计\n",
    "\n",
    "[BotSociety设计](https://app.botsociety.io/2.0/designs/60938d492b61046af4d28f70/edit?x=820&y=103)\n",
    "\n",
    "## 机器人架构\n",
    "\n",
    "机器人的逻辑核心采用[Rasa](https://rasa.com)框架。不过Rasa框架对中文支持有限，这里我编利用PaddleNLP的Transformer API生成embedding，作为每个句子的特征，整合到Rasa里面，提供中文的意图识别和基于CRF的命名实体识别支持。利用bert-wwm-chinese预训练模型，效果还不错。对接微信方面，我们使用了[自研的chat-operator中间件](https://xanthous.cn/posts/chat-operator)进行对接。我们用的是由句子互动提供的wxwork Wechaty puppet，直接对接企业微信。\n",
    "\n",
    "时间段识别我们使用了Facebook的duckling进行识别。机器人会根据识别出来的时间段，匹配企业微信日历里面相关时间的日程，找出可以预约的时间，给用户选择。当选择成功之后，会生成一个日程在企业微信日历中，帮店家管理每天的日程。\n",
    "\n",
    "## 飞桨使用体验\n",
    "\n",
    "我们使用了PaddleNLP的Transformer，我觉得整体和HuggingFace的Transformers包差距不是特别大。需要注意把数据转换成Paddle的Tensor才能调用模型（用 `paddle.to_tensor()` 方法）。如果时间充裕一些的话，我会考虑使用Transformer来做命名实体识别，因为我手头上针对这个场景的数据不够多，所以只能用Rasa提供的CRF算法来做。后面希望给理发店部署使用之后，可以多收集一些数据。\n",
    "\n",
    "## 当前遇到的问题和限制\n",
    "\n",
    "1. PaddleNLP的Bert模型forward时候返回embedding会出现不一致的情况，导致意图识别的准确率不稳定。可能和我的整合方法有关，希望能和Paddle的工程师沟通，看看能否改进\n",
    "2. 当前Wechaty和Rasa之间结合不够紧凑，由于时间关系暂时没有办法编写基于Python Wechaty的Rasa Connector组件。希望比赛后可以加上，配合添加企业微信的消息入口，可以制作用户添加机器人的欢迎语流程，更重要的是可以想办法把企业微信这边的external_id和Wechaty的contact id对上。\n",
    "3. 对于美业项目和项目的一些询问细节，需要和理发店的老板进一步沟通，当前只有比较简单的项目和耗时mapping，后续会需要根据实际情况多添加几轮问题，判断整个项目的总耗时（详细设计请见上方对话流程设计）\n",
    "4. 预约时间还有一些边界条件需要处理，比如说营业时间限制等。预约时间判断也写得比较仓促，可能会存在一些bug\n",
    "\n",
    "## 截图\n",
    "\n",
    "![](https://ai-studio-static-online.cdn.bcebos.com/5eaac316d2714083bc21ad673e13b421e0912e7a2abe4e3990bc7fa738bf698f)\n",
    "![](https://ai-studio-static-online.cdn.bcebos.com/8b03a67c8daa4886b27c9384a3104d174b88c3ad30fe425c8649e01ea5a9499a)\n",
    "![](https://ai-studio-static-online.cdn.bcebos.com/42499ba5776a49b6b6e63bc4084817e49a6e2f21aef2486c83ecd4ee56af41c9)\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 2.0.0b0 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
